// TX-One.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <windows.h>
#include <Psapi.h>
#include <stdint.h>

#ifdef _DEBUG
# define DEBUG
#endif

#ifdef DEBUG
# define DEBUG_LOG(format, ...) \
	fprintf(stderr, "DEBUG: "); \
	fprintf(stderr, format, ##__VA_ARGS__)
#else
# define DEBUG_LOG(...) ""
#endif

#define PROJECT_NAME "TX-One"
#define PROJECT_VERSION "1.5"
#define PROJECT_LEAD "James Hamilton, NF8I"

#define MIN(X,Y)	( ((X) < (Y)) ? (X) : (Y) )

#define CATBYTESCOUNT	4
#define	HRDWINDOWTITLECOUNT	3

// Pre-6.2
const uint8_t catBytesPre62[] = {
  0x54, 0x53, 0x2D, 0x35, 0x39, 0x30, 0x00, 0x00, 0x54, 0x58, 0x00, 0x00, 0x54, 0x58, 0x30, 0x00
};

const uint8_t newCatBytesPre62[] = {
  0x54, 0x53, 0x2D, 0x35, 0x39, 0x30, 0x00, 0x00, 0x54, 0x58, 0x00, 0x00, 0x54, 0x58, 0x31, 0x00
};

// 6.2 beta 3.1, 3.3
const uint8_t catBytes62Beta[] = {
  0x54, 0x00, 0x58, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x53, 0x00,
  0x2D, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x53, 0x00, 0x57, 0x00,
  0x48, 0x00, 0x30, 0x00, 0x39, 0x00, 0x00, 0x00
};

const uint8_t newCatBytes62Beta[] = {
  0x54, 0x00, 0x58, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x53, 0x00,
  0x2D, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x53, 0x00, 0x57, 0x00,
  0x48, 0x00, 0x30, 0x00, 0x39, 0x00, 0x00, 0x00
};

// 6.2 RC 1
const uint8_t catBytes62RC[] = {
	0x54, 0x00, 0x58, 0x00, 0x30, 0x00, 0x00, 0x00, 0x54, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x54, 0x00, 0x53, 0x00, 0x2D, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00
};

const uint8_t newCatBytes62RC[] = {
	0x54, 0x00, 0x58, 0x00, 0x31, 0x00, 0x00, 0x00, 0x54, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x54, 0x00, 0x53, 0x00, 0x2D, 0x00, 0x32, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00
};

// 6.2 Release
const uint8_t catBytes62Rel[] = {
	0x54, 0x00, 0x58, 0x00, 0x30, 0x00, 0x00, 0x00, 0x56, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x54, 0x00, 0x53, 0x00, 0x2D, 0x00, 0x39, 0x00, 0x39, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00
};

const uint8_t newCatBytes62Rel[] = {
	0x54, 0x00, 0x58, 0x00, 0x31, 0x00, 0x00, 0x00, 0x56, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x54, 0x00, 0x53, 0x00, 0x2D, 0x00, 0x39, 0x00, 0x39, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00
};

const uint8_t *catBytes[CATBYTESCOUNT] = {
	catBytesPre62, catBytes62Beta, catBytes62RC, catBytes62Rel
};

const uint8_t *newCatBytes[CATBYTESCOUNT] = {
	newCatBytesPre62, newCatBytes62Beta, newCatBytes62RC, newCatBytes62Rel
};

const DWORD catBytesSizes[CATBYTESCOUNT] = {
	sizeof(catBytesPre62), sizeof(catBytes62Beta), sizeof(catBytes62RC), sizeof(catBytes62Rel)
};

const char *catBytesDesc[CATBYTESCOUNT] = {
	"HRD pre-6.2", "HRD 6.2 Beta", "HRD 6.2 Release Candidate", "HRD 6.2 Release"
};

const LPCTSTR hrdWindowTitles[HRDWINDOWTITLECOUNT] = {
	L"HRD Rig Control - [TS-590]", L"HamRadioDeluxe - [TS-590]", L"HamRadioDeluxe"
};

BOOL replaceCatCommand(HANDLE hProc, DWORD dwCatAddress, const uint8_t *replacement, DWORD replacementSize);
DWORD GetModuleBase(HANDLE hProc, const wchar_t *szModuleName, MODULEINFO *pModInfo, DWORD szModInfo);
void PrintErrorMessage(DWORD dwErrorCode);
int PressAnyKey( void );

int _tmain(int argc, _TCHAR* argv[])
{
	int retVal = 1;
	DWORD pid, dwError;
	HWND hwnd = NULL;
	DWORD base, dwModSize;
	DWORD count = 0;
	DWORD dwModRead = 0, dwIndex = 0;
	unsigned char *pModBuffer = NULL;
	MODULEINFO modInfo;
	int catBytesIndex = -1;

	printf("%s v.%s\nby %s\n\n",PROJECT_NAME,PROJECT_VERSION,PROJECT_LEAD);

	DEBUG_LOG("DEBUGGING ENABLED!\n\n");

	for (count = 0; count < HRDWINDOWTITLECOUNT; count++) {
		hwnd = FindWindow(NULL,hrdWindowTitles[count]);
		if (hwnd)
			break;
	}
	if (!hwnd) {
		printf("Error: HRD Window not found! Searched for:\n");
		for (count = 0; count < HRDWINDOWTITLECOUNT; count++) {
			printf("\t%S\n",hrdWindowTitles[count]);
		}
		retVal = -1;
		goto finish;
	}
	DEBUG_LOG("Got window!\n");
	GetWindowThreadProcessId(hwnd,&pid);
	HANDLE pHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION,0,pid);//get permission to read
	if(!pHandle)//failed to get permission
	{
		dwError = GetLastError();
		printf("Error: Could not get process handle! ");
		PrintErrorMessage(dwError);
		printf("(Error code: %i)\n", dwError);
			retVal = -1;
			goto finish;
	}
	DEBUG_LOG("Got process!\n");
	if (GetModuleBase(pHandle, L"HamRadioDeluxe.exe", &modInfo, sizeof(modInfo)) != 0) {
		DEBUG_LOG("Trying to get base address from short name...\n");
		if (GetModuleBase(pHandle, L"HAMRAD~1.EXE", &modInfo, sizeof(modInfo)) != 0) {
			printf("Error: Could not find HamRadioDeluxe.exe module!\n");
			goto finish;
		}
	}
	base = (DWORD)modInfo.lpBaseOfDll;
	dwModSize = modInfo.SizeOfImage;
	DEBUG_LOG("Got Base Address: %x\n",base);

	pModBuffer = (unsigned char *)VirtualAlloc(NULL, dwModSize, MEM_COMMIT, PAGE_READWRITE);

	if (NULL == pModBuffer) {
		printf("Error! Failed to allocate memory for reading module HamRadioDeluxe.exe!\n");
		goto finish;
	}

	if (ReadProcessMemory(pHandle, (LPCVOID)base, pModBuffer, dwModSize, &dwModRead) == 0) {
		printf("Error! Failed to read memory from HamRadioDeluxe.exe!\n");
		goto finish;
	}

	for (dwIndex = 0; dwIndex < dwModRead - sizeof(catBytes62Beta); dwIndex++) {
		if (pModBuffer[dwIndex] == catBytesPre62[0]) {
			for (catBytesIndex = 0; catBytesIndex < CATBYTESCOUNT; catBytesIndex++) {
				if (memcmp(pModBuffer+dwIndex, catBytes[catBytesIndex], catBytesSizes[catBytesIndex]) == 0) {
					DEBUG_LOG("Found CAT Bytes in memory buffer at index: %i (Target Address: %p)\n", dwIndex, base + dwIndex);
					printf("Found CAT command for %s... patching...\n", catBytesDesc[catBytesIndex]);
					if (replaceCatCommand(pHandle, base+dwIndex, newCatBytes[catBytesIndex], catBytesSizes[catBytesIndex]))
						retVal = 0;
					else
						retVal = -1;
					DEBUG_LOG("Breaking out of array loop...\n");
					break;
				}
			}
			for (catBytesIndex = 0; catBytesIndex < CATBYTESCOUNT; catBytesIndex++) {
				if (memcmp(pModBuffer+dwIndex, newCatBytes[catBytesIndex], catBytesSizes[catBytesIndex]) == 0) {
					DEBUG_LOG("Found New CAT Bytes in memory buffer at index: %i (Target Address: %p)\n", dwIndex, base + dwIndex);
					printf("Found CAT command for %s already replaced! Skipping patch!\n", catBytesDesc[catBytesIndex]);
					retVal = 0;
					DEBUG_LOG("Breaking out of array loop...\n");
					break;
				}
			}
		}
		if (retVal < 1) {
			DEBUG_LOG("Breaking out of memory loop...\n");
			break;
		}
	}
	DEBUG_LOG("Finished searching memory buffer!\n");
	if (retVal == 0) {
		printf("Now go talk to someone!\n");
	} else {
		printf("Error: Could not patch HRD! This may be an unsupported version.\nIf this continues, please contact support for assistance.\n");
	}


finish:

	if (NULL != pModBuffer) {
		VirtualFree(pModBuffer, 0, MEM_RELEASE);
	}

	PressAnyKey();

	return retVal;
}

BOOL replaceCatCommand(HANDLE hProc, DWORD dwCatAddress, const uint8_t *replacement, DWORD replacementSize) {
	DWORD oldProt, oldProt2, dwError;
	SIZE_T szCount = 0;
	BOOL bRetVal = FALSE;

	if (VirtualProtectEx(hProc,(LPVOID)dwCatAddress,replacementSize,PAGE_EXECUTE_READWRITE,&oldProt)) {
		printf("Attempting byte replacement...\n");
		if (WriteProcessMemory(hProc,(LPVOID)dwCatAddress,replacement,replacementSize,&szCount)) {
			if (szCount == replacementSize) {
				printf("Successfully overwrote TX0 with TX1!\n");
				bRetVal = TRUE;
			} else {
				printf("Wrote %i bytes. Expected %i bytes. Close HRD and try again.  If this continues, please contact support.\n", szCount, replacementSize);
			}
		} else {
			dwError = GetLastError();
			printf("Failed to write command into memory! ");
			PrintErrorMessage(dwError);
			printf("(Error: %i)\n",dwError);
		}
		VirtualProtectEx(hProc,(LPVOID)dwCatAddress,replacementSize,oldProt,&oldProt2);
	} else {
		dwError = GetLastError();
		printf("Could not mark CAT command memory as writable! ");
		PrintErrorMessage(dwError);
		printf("(Error: %i)\n",dwError);
	}

	return bRetVal;
}

#pragma comment( lib, "psapi" )

DWORD GetModuleBase(HANDLE hProc, const wchar_t *szModuleName, MODULEINFO *pModInfo, DWORD szModInfo) 
{ 
   HMODULE *hModules = NULL; 
   wchar_t szBuf[50]; 
   DWORD cModules, dwError; 
   DWORD dwRetValue = -1;
   //------ 

   if (EnumProcessModules(hProc, hModules, 0, &cModules)) {
		DEBUG_LOG("Looks like %i modules\n",cModules);
   } else {
		dwError = GetLastError();
		printf("Error trying to find %S: ",szModuleName);
		PrintErrorMessage(dwError);
		printf("(Error: %i)\n",dwError);
		return -1;
   }
   hModules = new HMODULE[cModules/sizeof(HMODULE)]; 
   DEBUG_LOG("Allocated... now to enumerate!\n");

   if(EnumProcessModules(hProc, hModules, cModules/sizeof(HMODULE), &cModules)) { 
      for(unsigned int i = 0; i < cModules/sizeof(HMODULE); i++) { 
         if(GetModuleBaseName(hProc, hModules[i], szBuf, sizeof(szBuf))) { 
			DEBUG_LOG("Saw ModuleBaseName: %S\n",szBuf);
            if(wcscmp(szBuf,szModuleName) == 0) {
			   if (GetModuleInformation(hProc, hModules[i], pModInfo, szModInfo)) {
				   DEBUG_LOG("Got ModuleInfo: Base %p Size %i\n", pModInfo->lpBaseOfDll, pModInfo->SizeOfImage);
				   dwRetValue = 0;
			   }
               break;
            }
         }
      }
   }

   delete[] hModules; 

   return dwRetValue; 
}

int PressAnyKey( void )
  {
  DWORD        mode;
  HANDLE       hstdin;
  INPUT_RECORD inrec;
  DWORD        count;
  LPCWSTR prompt = L"Press any key to continue...";

  /* Set the console mode to no-echo, raw input, */
  /* and no window or mouse events.              */
  hstdin = GetStdHandle( STD_INPUT_HANDLE );
  if (hstdin == INVALID_HANDLE_VALUE
  || !GetConsoleMode( hstdin, &mode )
  || !SetConsoleMode( hstdin, 0 ))
    return 0;

  /* Instruct the user */
  WriteConsole(
    GetStdHandle( STD_OUTPUT_HANDLE ),
    prompt,
    lstrlen( prompt ),
    &count,
    NULL
    );

  FlushConsoleInputBuffer( hstdin );

  /* Get a single key RELEASE */
  do ReadConsoleInput( hstdin, &inrec, 1, &count );
  while ((inrec.EventType != KEY_EVENT) || inrec.Event.KeyEvent.bKeyDown);

  /* Restore the original console mode */
  SetConsoleMode( hstdin, mode );

  return inrec.Event.KeyEvent.wVirtualKeyCode;
  }

void PrintErrorMessage(DWORD dwErrorCode) {
	LPVOID lpMsgBuf;

	FormatMessage( 
		FORMAT_MESSAGE_ALLOCATE_BUFFER | 
		FORMAT_MESSAGE_FROM_SYSTEM | 
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		dwErrorCode,
		0,
		(LPTSTR) &lpMsgBuf,
		0,
		NULL 
	);

	_tprintf(TEXT("%s"),lpMsgBuf);
	LocalFree(lpMsgBuf);
}